Skip to content

✨ feat: add a passkey type - #82

Merged
edgarmesquita merged 1 commit into
mainfrom
feat/passkey-type
Sep 29, 2026
Merged

edgarmesquita merged 1 commit into
mainfrom
feat/passkey-type

Conversation

@edgarmesquita

Copy link
Copy Markdown
Contributor

A record of a passkey, not the passkey.

Why a record

The private half of a passkey never leaves its authenticator — that is the point of one — so no vault can hold it, and a web page cannot register as the authenticator that signs with it (that takes a browser extension or a native credential provider). So Keeper cannot be where a passkey lives, and this type does not pretend to be.

What people actually lose track of is where each one lives, and what gets them back in when that device is gone. That is what the type records:

Field Kind Why
Site url
Conta username also becomes the row's secondary line, for free
Guardada em text + options iCloud, Google, Windows Hello, 1Password, Bitwarden, a YubiKey… still free text
Aparelho text matters when the key does not sync
Criada em date registeredAt, not createdAt, to stay clear of the item's own field
Códigos de recuperação multilineSecret the way back in without the device

The feature hiding in the search

Search indexes every non-secret field, so the authenticator and the device are searchable. Typing yubikey lists exactly what that key opens — the question someone asks the day they lose it.

My first draft broke that without noticing: I had put yubikey and chave de segurança among the type's keywords. A keyword matches every item of the type, so every passkey would have answered that search, iCloud ones included, and the answer would have been a lie. The keywords now describe the type — webauthn, fido2, and the names each platform uses in Portuguese (chave-senha for Apple, chave de acesso for Google and Microsoft) — and never where one lives. A comment in the model says why.

Tests

Three unit tests in search.test.ts, with a YubiKey passkey beside an iCloud one:

  • found by the name each platform gives it;
  • yubikey returns the YubiKey one and only it;
  • the recovery codes are never indexed.

Three mutations, three failures — including putting my own mistake back:

Mutation Result
yubikey as a type keyword 1 failed
no keywords 1 failed
recovery codes as plain multiline (indexable) 1 failed

Four smoke checks create a passkey through the real form: the type is offered under development secrets, the wizard filter finds it by chave de acesso (and hides Chave SSH), the form renders where-it-lives and recovery, and after saving, searching yubikey in the real UI returns it with the account on the row. The smoke comment is explicit that it proves the wiring, not "only that" — its vault holds a single passkey, so the unit test carries that half.

npm test 390 green · build clean · smoke 234 green, up from 230.

A record of a passkey, not the passkey. Its private half never leaves
the authenticator — that is the point of one — so no vault can hold
it, and a web page cannot become the authenticator that signs with it.

What people lose track of is where each one lives and what gets them
back in when that device is gone, so the type records exactly that:
the site, the account, the authenticator, the device when the key does
not sync, when it was created, and the recovery codes.

The authenticator and the device are plain text on purpose. Search
reads non-secret fields, so typing "yubikey" lists what that key opens,
which is the question someone asks the day they lose it. The first
draft put "yubikey" among the type's keywords, which would have made
every passkey answer that search, iCloud ones included; the keywords
now say what the type is (webauthn, the names Apple, Google and
Microsoft give it in Portuguese) and never where one lives.
@edgarmesquita
edgarmesquita merged commit 30d0692 into main Sep 29, 2026
3 checks passed
@edgarmesquita
edgarmesquita deleted the feat/passkey-type branch September 29, 2026 22:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant